#-*- coding: utf-8 -*-
"""
TP10 - Compresseur/décompresseur pas foireux mais très inefficace : 
quand le dico n'est pas en dur dans le programme, c'est très mauvais. 

Par Mathieu LECLERQ et Guillaume HUYSMANS, 2013.
"""
import argparse
import pickle


class badADNformat(Exception):
	pass

def lire(fn):
	"""
	Lit un fichier dont le nom est fn.
	Attention, des exceptions peuvent être générées.
	"""
	f = open(fn)
	return f.read()
	#pas de f.close() vu que la variable sera détruite...

def ecrire(fn, c):
	"""
	Écrit un fichier nommé fn avec comme contenu c.
	Attention, des exceptions peuvent être générées.
	"""
	f = open(fn, 'w')
	f.write(c)
	f.close() #un peu inutile...

def lzComp(s):
	"""
	Compacte la chaine s selon l'algorithme LZ.
	Les espaces et passages à la ligne sont préservés.
	Retourne le dico puis la liste (sous forme de chaine).
	"""
	dico = [] #FIXME utiliser un dico pour aller plus vite.
	res = ''
	for line in s.split('\n'):
		for word in line.split(' '):
			if not word in dico:
				dico.append(word)
			res += str(dico.index(word)) + ' '
		res = res[:-1] + '\n'
	return dico, res[:-1]

def lzDecomp(dico, s):
	"""
	Décompacte la chaine s selon l'algorithme LZ.
	Les espaces et passages à la ligne sont préservés.
	"""
	res = ''
	for line in s.split('\n'):
		for word in line.split(' '):
			res += dico[int(word)] + ' '
		res = res[:-1] + '\n'
	return res[:-1]

"""def stockerDico(dico):
	ret = ''
	for x in dico:
		ret += x + '|'
	return ret[:-1]"""

def nettoyer(s):
	"""
	Ne garde que les caractères ASCII (rappel : de 0 à 127).
	Totalement inutile dans l'état actuel du programme.
	"""
	ret = ''
	for x in s:
		n = ord(x)
		if n <= 127:
			ret += x
	return ret

def adnComp(s):
	"""Basé sur RLE mais ne marche qu'avec l'ADN."""
	last = ''
	ct = 0
	res = ''
	s = s.replace('\n', '').upper()
	for c in s:
		if not c in ['A', 'T', 'C', 'G']:
			raise badADNformat("Mais c'est pas de l'ADN, ça !")
		if c != last:
			#on a affaire à un nouveau caractère
			#on retient le précédent, et si on n'en avait pas
			#encore eu, on ne fait rien.
			if ct > 0:
				res += last + str(ct)
			last = c
			ct = 1
		else:
			ct += 1
	return res + c + str(ct) #pas oublier le dernier

def adnDecomp(s):
	ct = 0 #nombre de fois
	r = '' #caractère à répéter
	res = '' #résultat
	for c in s:
		if c in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
			#on est tombé sur un chiffre qui compose le nombre de répét's.
			#on va donc l'ajouter à droite du compteur : pour ça,
			#on multiplie par 10 et on ajoute le chiffre.
			ct = ct*10 + int(c)
		else:
			if not c in ['A', 'T', 'C', 'G']:
				raise badADNformat("Mais c'est pas de l'ADN, ça !")
			#on est sur une lettre, donc on va stocker n fois la précédente.
			#ensuite on remet l'accumulateur à 0 et on retient le caractère.
			res += ct*r
			r = c
			ct = 0
	return res + ct*r #pas oublier le dernier

def binComp(s):
	"""
	Compression façon fax : on commence par un truc défini au départ 
	(1 ou 0) puis on ne note que le nombre d'apparitions sur un octet.
	Si ça dépasse 255, on met 0 puis la suite (et ça peut se faire 
	plusieurs fois...). En gros, on ne note même plus les données mais 
	seulement les répétitions.
	"""
	#TODO
	pass

def binDecomp(s):
	#TODO
	pass

def fleursdumal():
	#print compacter('bonjour tout bonjour le bonjour MONDE boNjour')
	orig = lire('fleursdumal.txt')
	dico, comp = compacter(orig)
	txt = decompacter(dico, comp)
	ecrire('essai_dico.txt', stockerDico(dico))
	ecrire('essai_comp.txt', comp)
	l_txt = len(txt)
	l_dico = len(stockerDico(dico))
	l_comp = len(comp)
	l_orig = len(orig)
	print "longueur original\t: %d" % l_txt
	print "longueur dictionnaire\t: %d" % l_dico
	print "longueur compacté\t: %d" % l_comp
	print "longueur tout\t\t: %d" % (l_dico+l_comp)
	print "rapport tot/orig\t: %05.2f%%" % (float(l_txt+l_dico)/l_orig*100)
	print "rapport comp/orig\t: %05.2f%%" % (float(l_comp)/l_orig*100)




parser = argparse.ArgumentParser(description=__doc__)
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument('-c', action='store_true', help='compresser')
action.add_argument('-d', action='store_false', help='décompresser')
parser.add_argument('-t', required=True, help="algorithme (ADN, LZ)")
parser.add_argument('-in', dest='inp', required=True, help="fichier d'entrée")
parser.add_argument('-out', help="fichier de sortie")
args = parser.parse_args()

#TODO pour gérer d'autres algos, créer une liste avec des pointeurs de fonctions.

#si aucun fichier de sortie n'est précisé, on base son nom sur l'entrée
if not isinstance(args.out, str):
	args.out = args.inp + '.out'

#entrée
inp = lire(args.inp)

#calculs
if args.c:
	if args.t=='ADN':
		out = adnComp(inp)
	elif args.t=='LZ':
		dico, out = lzComp(inp)
		f = open(args.out + '.dic', 'w')
		pickle.dump(dico, f)
		f.close()
else:
	if args.t=='ADN':
		out = adnDecomp(inp)
	elif args.t=='LZ':
		f = open(args.inp + '.dic')
		dico = pickle.load(f)
		f.close()
		out = lzDecomp(dico, inp)

#sortie
ecrire(args.out, out)

#stats
print "Ratio de différence : %5.2f%%" % (float(len(out)-len(inp))/len(inp)*100)

"""
TODO calculer le temps ?
TODO gestion des exceptions : accès aux fichiers, ADN, arguments (!)

Il vous est demandé de gérer finement les erreurs pouvant se produire :
– Si le nombre de paramètres fournis n’est pas correct ou que les paramètres sont incorrects, générez
une exception de type ValueError et affichez un message d’aide concernant l’utilisation du script.
– Si le fichier source n’existe pas, gérez l’exception concernée.
– Si le fichier source, lors de la décompression, n’est pas dans le format attendu, affichez une erreur
et générez une exception du type TypeError.
– D’autres erreurs spécifiques aux techniques seront spécifiées plus loin.
"""
